/****************************************************************************
 ■ PhotoFrame
   2010(C) Mr.Honey
****************************************************************************/

#include <p33Fxxxx.h>

#define FCY_MHZ 4

#if (FCY_MHZ == 4)
#define FCY  3685000UL
#elif (FCY_MHZ == 10)
#define FCY 10018594UL
#elif (FCY_MHZ == 40)
#define FCY 39613750U
#endif
#include <libpic30.h>

//=================================================================

typedef unsigned char U8;
typedef unsigned short U16;
typedef unsigned long U32;

#define BOOL	U8
#define TRUE	1
#define FALSE	0

//=================================================================

// Configuration
_FBS(BWRP_WRPROTECT_OFF & BSS_NO_BOOT_CODE)
_FGS(GSS_OFF & GCP_OFF & GWRP_OFF)
_FOSC(POSCMD_NONE & OSCIOFNC_ON & FCKSM_CSECMD)
_FWDT(FWDTEN_OFF & WINDIS_OFF & WDTPRE_PR32 & WDTPOST_PS1024)
_FPOR(FPWRT_PWR16)
_FICD(JTAGEN_OFF & ICS_PGD2)

#if (FCY_MHZ == 4)
_FOSCSEL(IESO_ON & FNOSC_FRC)
#else
_FOSCSEL(IESO_ON & FNOSC_FRCPLL)
#endif

_FUID0(0x00)
_FUID1(0x00)
_FUID2(0x00)
_FUID3(0x00)

//=================================================================

#define LED1		LATC
#define LED1_DIGs	LATB
#define LED1_DIG_SH	5

#define LED1_DIG1	_LATB5
#define LED1_DIG2	_LATB6
#define LED1_DIG3	_LATB7
#define LED1_DIG4	_LATB8

#define LED2_R		_LATA4
#define LED2_B		_LATA9
#define LED3_R		_LATA8
#define LED3_B		_LATB4
#define LED4_R		_LATA2
#define LED4_B		_LATA3

#define SENS_TAMP	0b00010 // AN2
#define SENS_TEMP	0b01001 // AN9
#define SENS_LIGHT	0b01010 // AN10

#define SW			_RB9
#define SWCNIE		CNEN2bits.CN21IE

#define LED1_DPM	0b10000000
#define VREF		3000UL
#define REFRESH		0x800

//=================================================================

#define GETTING_PROD	1000	// 定期起床間隔(ms)
#define TEMPDISPTIME	5000	// 一時表示時間(ms)
#define DISPON_LIGHT	400		// 表示ON光量(AD値)
#define TSAMNUM			8		// 温度平均化サンプル数(2の乗数)
#define DETECT_TMP		10		// 変化検出温度(±0.01℃)
#define TEND_LV3		36		// レベル3変化時間(sec) 目安：1時間で10℃
#define TEND_LV2		180		// レベル2変化時間(sec) 目安：5時間で10℃
#define TEND_LV1		468		// レベル1変化時間(sec) 目安：13時間で10℃
#define STARTUP			10		// 変化検出抑制時間(定期起床回数)

#define LIGHT_MIN		1000	// 暗い時の光量AD値
#define LIGHT_MAX		4000	// 明るい時の光量AD値

#if 1 // 暗め
#define BRIGHT_MIN		1		// 暗い時の明るさ(ディーティ比)
#define BRIGHT_MAX		25		// 明るい時の明るさ(ディーティ比)
#else // 明るめ
#define BRIGHT_MIN		2		// 暗い時の明るさ(ディーティ比)
#define BRIGHT_MAX		40		// 明るい時の明るさ(ディーティ比)
#endif

//=================================================================

static void Initialize(void);
static void Run(void);

static void Actinography(void);
static void Thermometry(void);
static void Display(BOOL);
static void WorkToWait(void);

static void PrintData(void);
static void Format3(U16, U8, U8);
static void Format4(U16, U8);
static void SetDP(U8);
static void SetTend(void);
static void SetLEDs(U8);

static void DelayMs(U16 ms);

//=================================================================

#define MODE_T3DIG_C	0	// 温度表示(3桁)+c表示
#define MODE_T3DIG		1	// 温度表示(3桁)
#define MODE_T4DIG		2	// 温度表示(4桁)
#define MODE_TADVAL		3	// 入力AD値表示(4桁)
#define MODE_TVOLT		4	// 入力電圧表示(4桁)
#define MODE_GAIN		5	// アンプゲイン表示(4桁)
#define MODE_LIGHT		6	// 光量AD値表示(4桁)
#define MODE_COUNT		7

#define LED2_RM		0b000001	// 上赤
#define LED2_BM		0b000010	// 上青
#define LED3_RM		0b000100	// 中赤
#define LED3_BM		0b001000	// 中青
#define LED4_RM		0b010000	// 下赤
#define LED4_BM		0b100000	// 下青

#define DP_NONE		0b0000
#define DP_DIG1		0b1000	// 1桁目ドット
#define DP_DIG2		0b0100	// 2桁目ドット
#define DP_DIG3		0b0010	// 3桁目ドット
#define DP_DIG4		0b0001	// 4桁目ドット

#define CHAR_SP		10		// 空白
#define CHAR_C		11		// c
#define CHAR_COUNT	12

//=================================================================

// 7セグ表示データ
const U8 s_Font[CHAR_COUNT] = {
	0b1101111, 0b0101000, 0b0110111, 0b0111110,
	0b1111000, 0b1011110, 0b1011111, 0b1101100,
	0b1111111, 0b1111110, 0b0000000, 0b0010011
};

static BOOL s_fDisp = FALSE;	// 表示中かどうかを示すフラグ
static BOOL s_fPush = FALSE;	// SWが押下を示すフラグ
static int s_nMode = MODE_T3DIG_C;	// 表示モード

static U16 s_nLight = 0;		// 光量12BitAD取得値
static U16 s_nTherm1 = 0;		// 温度12BitAD取得値(増幅後)
static U16 s_nTherm2 = 0;		// 温度12BitAD取得値(増幅前)
static U16 s_nTempt = 0;		// 平均化温度値
static int s_nTend = 0;			// 温度変化傾向値(-3 〜 +3)

static U16 s_nTherms[TSAMNUM] = { 0 };	// 温度サンプル
static U16 s_nSamCnt = 0;		// サンプルカウンタ

static U16 s_nDetTmp = 0;		// 検出基準温度
static int s_nDetCnt = -STARTUP - 1;// 変化検出カウンタ

static U8 s_Data[5] = { 0 };	// 表示データ

// 7セグ表示用
static struct DYNDRVINFO {
	BOOL fOn;		// 表示状態
	int nIndex;		// 桁位置
	U16 nTmrOn;		// 表示ON期間タイマー値
	U16 nTmrOff;	// 表示OFF期間タイマー値
} s_DynDrv = { FALSE, 0, 0, 0 };

//=================================================================

// ●メインエントリ
int main(void)
{
	Initialize();

	// 起動表示
	LED2_R = LED2_B = 1;
	DelayMs(1000);
	LED2_R = LED2_B = 0;
	LED3_R = LED3_B = 1;
	DelayMs(1000);
	LED3_R = LED3_B = 0;
	LED4_R = LED4_B = 1;
	DelayMs(1000);
	LED4_R = LED4_B = 0;

	// 実行開始
	Run();
	return 0;
}

// ●システム初期化
static void Initialize(void)
{
	// モジュール有効無効設定
	PMD1 = 0xFFFE; // AD1(AD1MD)のみON
	PMD2 = 0xFFFF;

	// クロック設定
#if (FCY_MHZ == 10)
	// FOSC:20.0372MHz Fcy:10.01859MHz
	CLKDIVbits.PLLPRE = 2;	// N1:1/4 -- 1.8425MHz
	CLKDIVbits.PLLPOST = 3;	// N2:1/8 -- 20MHz
	PLLFBDbits.PLLDIV = 85;	//  M: 87 -- 160.2975MHz
#elif (FCY_MHZ == 40)
	// FOSC:79.2275MHz Fcy:39.61375MHz
	CLKDIVbits.PLLPRE = 0;	// N1:1/2 -- 3.685MHz
	CLKDIVbits.PLLPOST = 0;	// N2:1/2 -- 79.2275MHz
	PLLFBDbits.PLLDIV = 41;	//  M: 43 -- 158.455MHz
#endif

	// ポートクリア
	PORTA = LATA = 0;
	PORTB = LATB = 0;
	PORTC = LATC = 0;

	// デジアナ設定
	AD1PCFGL = 0b1110000111000000;

	// プルアップ設定
	CNPU1 = 0b1000000000000000; // Open
	CNPU2 = 0b0000000000111001; // Open,SW

	// 方向設定
	TRISA = 0b1111100001100011;
	TRISB = 0b1111111000001111;
	TRISC = 0b1111111100000000;

	// ADコンバータ
	// TAD: Min.117.6ns, TSAMP: Min.3TAD, tCONV: 14TAD
	AD1CON1 = 0b1000010011100000; // ADON:1 AD12B:1 SSRC:Auto ASAM:Manual
	AD1CON2 = 0b0010000000000000; // VCFG:VREF+&AVSS ALTS:Sample A
	AD1CON3bits.ADRC = 0;

#if (FCY_MHZ == 4)
	// TCY = 1/Fcy = 1/3.685MHz = 271.3nsec
	// TAD = TCY*(ADCS + 1) = 271.3nsec * (0 + 1) = 271.3ns
	// 全工程 = (4 + 14 + 2)*271.3ns = 5.4us
	AD1CON3bits.ADCS = 0;
	AD1CON3bits.SAMC = 4; // 4TAD
#elif (FCY_MHZ == 10)
	// TCY = 1/Fcy = 1/10.01859MHz = 99.8nsec
	// TAD = TCY*(ADCS + 1) = 99.8nsec * (1 + 1) = 199.6ns
	// 全工程 = (5 + 14 + 2)*199.6ns = 4.2us
	AD1CON3bits.ADCS = 1;
	AD1CON3bits.SAMC = 5; // 5TAD
#elif (FCY_MHZ == 40)
	// TCY = 1/Fcy = 1/39.61375MHz = 25.2nsec
	// TAD = TCY*(ADCS + 1) = 25.2nsec * (4 + 1) = 126ns
	// 全工程 = (8 + 14 + 2)*126ns = 3us
	AD1CON3bits.ADCS = 4;
	AD1CON3bits.SAMC = 8; // 8TAD
#endif
	
	// 入力変化通知割込
	IEC1bits.CNIE = 1;
}

// ●実行ループ
static void Run(void)
{
	int nDispCount = 0;
	
	for(;;)
	{
		// 測定
		Actinography();
		Thermometry();

		// スイッチ押下処理
		if (s_fPush)
		{
			if (s_fDisp) {
				s_nMode = (s_nMode + 1) % MODE_COUNT;
			}
			nDispCount = TEMPDISPTIME / GETTING_PROD + 1;
		}

		// 明るいorテストモードの時は表示
		if (s_nLight >= DISPON_LIGHT || s_nMode >= MODE_TADVAL) {
			nDispCount = 3; // マージン
		}
		else if (nDispCount > 0) {
			nDispCount--;
		}

		// 表示切替
		if (nDispCount > 0)
		{
			PrintData();
			Display(TRUE);
		}
		else {
			Display(FALSE);
		}

		// 待機処理
		WorkToWait();
	}
}

//=================================================================

// ●光量取得
static void Actinography(void)
{
	IEC0bits.T3IE = 0; // ノイズ混入防止

	AD1CON2bits.VCFG = 0b000; // AVDD/AVSS

	// サンプル・変換
	AD1CHS0bits.CH0SA = SENS_LIGHT;
	AD1CON1bits.DONE = 0;
	AD1CON1bits.SAMP = 1;
	while (!AD1CON1bits.DONE);

	IEC0bits.T3IE = 1;

	// 前回値との平均とする
	s_nLight = (s_nLight + ADC1BUF0) / 2;
}

// ●温度取得
static void Thermometry(void)
{
	int i, n;

	// 表示とかぶらないように(表示乱れ＆ノイズ混入防止)
	if (T1CONbits.TON)
	{
		U16 nCount = ((s_DynDrv.nTmrOn < s_DynDrv.nTmrOff)?
			s_DynDrv.nTmrOn : s_DynDrv.nTmrOff) + 8;
		while (TMR1 > nCount);
	}
	IEC0bits.T3IE = 0;

	AD1CON2bits.VCFG = 0b001; // VREF+/AVSS

	// サンプル・変換(先に出力側)
	AD1CHS0bits.CH0SA = SENS_TAMP;
	AD1CON1bits.DONE = 0;
	AD1CON1bits.SAMP = 1;
	while (!AD1CON1bits.DONE);
	s_nTherm1 = ADC1BUF0;

	// サンプル・変換(後に入力側)
	AD1CHS0bits.CH0SA = SENS_TEMP;
	AD1CON1bits.DONE = 0;
	AD1CON1bits.SAMP = 1;
	while (!AD1CON1bits.DONE);
	s_nTherm2 = ADC1BUF0;

	IEC0bits.T3IE = 1;

	// 温度サンプリング
	s_nTherms[s_nSamCnt % TSAMNUM] = s_nTherm1;
	if (++s_nSamCnt == 0) {
		s_nSamCnt = TSAMNUM;
	}
	
	// 平均化
	n = (s_nSamCnt < TSAMNUM)? s_nSamCnt : TSAMNUM;
	s_nTempt = 0;
	for (i = 0; i < n; i++) {
		s_nTempt += s_nTherms[i];
	}
	s_nTempt /= n;

	// 変化傾向
	if (++s_nDetCnt > 0)
	{
		int nAmount = s_nTempt - s_nDetTmp;
		if (nAmount >= DETECT_TMP || nAmount <= -DETECT_TMP)
		{
			// 変化を検出
			if (s_nDetCnt <= TEND_LV3) {
				s_nTend = (nAmount >= 0)? 3 : -3;
			}
			else if (s_nDetCnt <= TEND_LV2) {
				s_nTend = (nAmount >= 0)? 2 : -2;
			}
			else {
				s_nTend = (nAmount >= 0)? 1 : -1;
			}
			s_nDetTmp = s_nTempt;
			s_nDetCnt = 0;
		}
		else
		{
			// 変化なし
			if (s_nDetCnt >= TEND_LV1)
			{
				s_nTend = 0;
				s_nDetTmp = s_nTempt;
				s_nDetCnt = 0;
			}
			else if (s_nDetCnt >= TEND_LV2)
			{
				if (s_nTend > 1) {
					s_nTend = 1;
				}
				else if (s_nTend < -1) {
					s_nTend = -1;
				}
			}
			else if (s_nDetCnt >= TEND_LV3)
			{
				if (s_nTend > 2) {
					s_nTend = 2;
				}
				else if (s_nTend < -2) {
					s_nTend = -2;
				}
			}
		}
	}
	else {
		// 検出抑制中
		s_nDetTmp = s_nTempt;
	}
}

// ●表示切替
static void Display(BOOL fOn)
{
	if (fOn)
	{
		// 表示時は常に更新
		LED2_R = (s_Data[0] & LED2_RM)? 1 : 0;
		LED2_B = (s_Data[0] & LED2_BM)? 1 : 0;
		LED3_R = (s_Data[0] & LED3_RM)? 1 : 0;
		LED3_B = (s_Data[0] & LED3_BM)? 1 : 0;
		LED4_R = (s_Data[0] & LED4_RM)? 1 : 0;
		LED4_B = (s_Data[0] & LED4_BM)? 1 : 0;

		// 光量からディユーティー比->タイマカウンタを求める
		U32 nLevel = (s_nLight < LIGHT_MAX)?
			((s_nLight > LIGHT_MIN)? s_nLight - LIGHT_MIN : 0) : (LIGHT_MAX - LIGHT_MIN);

		U32 nRatio = ((BRIGHT_MAX - BRIGHT_MIN) * nLevel) / (LIGHT_MAX - LIGHT_MIN) + BRIGHT_MIN;
		U16 nCount = (U16)((REFRESH * nRatio) / 100);

		s_DynDrv.nTmrOn = 0xFFFF - nCount;
		s_DynDrv.nTmrOff = 0xFFFF - (REFRESH - nCount);

		// リフレッシュ用タイマ設定
		if (!s_fDisp)
		{
			// OFF->ON
			PMD1bits.T1MD = 0;

			s_DynDrv.fOn = FALSE;
			s_DynDrv.nIndex = 0;

			TMR1 = 0xFFFF;
			IFS0bits.T1IF = 0;
			IEC0bits.T1IE = 1;
			T1CON = 0b1010000000000000;

			s_fDisp = TRUE;
		}
	}
	else
	{
		// リフレッシュ用タイマ設定
		if (s_fDisp)
		{
			// ON->OFF
			LED1 = 0;
			LED1_DIGs &= ~(0b1111 << LED1_DIG_SH);

			LED2_R = LED2_B = 0;
			LED3_R = LED3_B = 0;
			LED4_R = LED4_B = 0;

			T1CONbits.TON = 0;
			IEC0bits.T1IE = 0;
			PMD1bits.T1MD = 1;

			s_fDisp = FALSE;
		}
	}
}

// ●待機処理
static void WorkToWait(void)
{
	s_fPush = FALSE;

	SWCNIE = 1;

	if (!s_fDisp)
	{
		// スリープモードで待機
		RCONbits.SWDTEN = 1;
		Sleep();
		RCONbits.SWDTEN = 0;
		DelayMs(1);
	}
	else
	{
		// 通常動作で待機
		int i;
		for (i = 0; !s_fPush && i < GETTING_PROD; i++) {
			DelayMs(1);
		}
	}

	SWCNIE = 0;
	
	// チャタリング防止
	if (s_fPush) {
		DelayMs(100);
	}
}

// ●表示処理
static void PrintData(void)
{
	switch (s_nMode)
	{
	case MODE_T3DIG_C:	// 温度表示(3桁)+c表示
		Format3((s_nTempt + 5) / 10, CHAR_C, DP_DIG2);
		SetTend();
		break;
	case MODE_T3DIG:	// 温度表示(3桁)
		Format3((s_nTempt + 5) / 10, CHAR_SP, DP_DIG2);
		SetTend();
		break;
	case MODE_T4DIG:	// 温度表示(4桁)
		Format4(s_nTempt, DP_DIG2);
		SetTend();
		break;
	case MODE_TADVAL:	// 入力AD値表示(4桁)
		Format4(s_nTherm2, DP_NONE);
		SetLEDs(LED2_RM | LED4_BM);
		break;
	case MODE_TVOLT:	// 入力電圧表示(4桁)
		Format4((U16)(((U32)s_nTherm2 * VREF) / 4095UL), DP_DIG1);
		SetLEDs(LED2_RM | LED4_BM);
		break;
	case MODE_GAIN:		// アンプゲイン表示(4桁)
		Format4((U16)((U32)s_nTherm1 * 1000UL / (U32)s_nTherm2), DP_DIG1);
		SetLEDs(LED2_RM | LED4_BM);
		break;
	case MODE_LIGHT:	// 光量AD値表示(4桁)
		Format4(s_nLight, DP_NONE);
		SetLEDs(0);
		break;
	}
}

// ●3桁フォーマット
static void Format3(U16 nVal, U8 c, U8 nDPs)
{
	s_Data[4] = s_Font[c];
	s_Data[3] = s_Font[nVal % 10];
	nVal /= 10;
	s_Data[2] = s_Font[(nVal > 0 || (nDPs >= DP_DIG2))? nVal % 10 : CHAR_SP];
	nVal /= 10;
	s_Data[1] = s_Font[(nVal > 0 || (nDPs >= DP_DIG1))? nVal % 10 : CHAR_SP];

	SetDP(nDPs);
}

// ●4桁フォーマット
static void Format4(U16 nVal, U8 nDPs)
{
	s_Data[4] = s_Font[nVal % 10];
	nVal /= 10;
	s_Data[3] = s_Font[(nVal > 0 || (nDPs >= DP_DIG3))? nVal % 10 : CHAR_SP];
	nVal /= 10;
	s_Data[2] = s_Font[(nVal > 0 || (nDPs >= DP_DIG2))? nVal % 10 : CHAR_SP];
	nVal /= 10;
	s_Data[1] = s_Font[(nVal > 0 || (nDPs >= DP_DIG1))? nVal % 10 : CHAR_SP];

	SetDP(nDPs);
}

// ●ドット設定
static void SetDP(U8 nDPs)
{
	if (nDPs & DP_DIG1) s_Data[1] |= LED1_DPM;
	if (nDPs & DP_DIG2) s_Data[2] |= LED1_DPM;
	if (nDPs & DP_DIG3) s_Data[3] |= LED1_DPM;
	if (nDPs & DP_DIG4) s_Data[4] |= LED1_DPM;
}

// ●傾向表示
static void SetTend(void)
{
	if (s_nDetCnt >= 0)
	{
		switch (s_nTend)
		{
		case 3: // 急上昇
			SetLEDs(LED2_RM | LED3_RM | LED4_RM);
			break;
		case 2: // 中上昇
			SetLEDs(LED2_RM | LED3_RM);
			break;
		case 1: // 低上昇
			SetLEDs(LED2_RM);
			break;
		case -1: // 低下降
			SetLEDs(LED4_BM);
			break;
		case -2: // 中下降
			SetLEDs(LED4_BM | LED3_BM);
			break;
		case -3: // 急下降
			SetLEDs(LED4_BM | LED3_BM | LED2_BM);
			break;
		default:
			SetLEDs(0);
			break;
		}
	}
	else {
		// 検出抑制中は点滅させる
		SetLEDs((s_nDetCnt & 1)? LED2_BM : LED4_RM);
	}
}

// ●パターン表示
static void SetLEDs(U8 nPat)
{
	s_Data[0] = nPat;
}

// ●遅延
static void DelayMs(U16 ms)
{
	while (ms >= 100)
	{
		__delay_ms(100);
		ms -= 100;
	}
	if (ms != 0) {
		__delay_ms(ms);
	}
}

//=================================================================

// ●TMR1 Timer 1 expired
void _ISRFAST _T1Interrupt(void)
{
	T1CONbits.TON = 0;

	if (!s_DynDrv.fOn)
	{
		// 表示する
		LED1 = s_Data[s_DynDrv.nIndex + 1];
		LED1_DIGs |= 1 << (s_DynDrv.nIndex + LED1_DIG_SH);
		s_DynDrv.fOn = TRUE;

		TMR1 = s_DynDrv.nTmrOn;
	}
	else
	{
		// 消去する
		LED1_DIGs &= ~(0b1111 << LED1_DIG_SH);
		s_DynDrv.fOn = FALSE;
		s_DynDrv.nIndex = (s_DynDrv.nIndex + 1) & 0x3;

		TMR1 = s_DynDrv.nTmrOff;
	}

	IFS0bits.T1IF = 0;
	T1CONbits.TON = 1;
}

// ●CN Input change interrupt
void _ISRFAST _CNInterrupt(void)
{
	int i = 0, j = 0, k = 0;
	do {
		if (!SW) j++; // 接触している
		else k++;     // 接触していない
	}
	while (++i < 11);
	if (j > k) {
		s_fPush = TRUE;
	}

	IFS1bits.CNIF = 0;
}
